{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# <center>第7章 调试与测试</center>\n",
    "\n",
    "<br>\n",
    "\n",
    "- [7.1 调试方法](#7.1-调试方法)\n",
    "  - [7.1.1 利用 `print`调试程序](#7.1.1-利用-print调试程序)\n",
    "  - [7.1.2 利用 `logging`调试程序](#7.1.2-利用-logging调试程序)\n",
    "  - [7.1.3 `pdb`调试器](#7.1.3-pdb调试器)\n",
    "- [7.2 异常处理](#7.2-异常处理)\n",
    "  - [7.2.1 异常的原因](#7.2.1-异常的原因)\n",
    "  - [7.2.2 断言](#7.2.2-断言)\n",
    "  - [7.2.3 异常处理](#7.2.3-异常处理)\n",
    "  - [7.2.4 异常的类型](#7.2.4-异常的类型)\n",
    "- [7.3 单元测试*](#7.3-单元测试*)\n",
    "  - [7.3.1 单元测试的概念及工具](#7.3.1-单元测试的概念及工具)\n",
    "  - [7.3.2 `unittest`基础](#7.3.2--unittest基础)\n",
    "  - [7.3.3 创建测试用例](#7.3.3-创建测试用例)\n",
    "  - [7.3.4 运行测试用例](#7.3.4-运行测试用例)\n",
    "  - [7.3.5 测试套件的创建与执行](#7.3.5-测试套件的创建与执行)\n",
    "  - [7.3.6 测试设施](#7.3.6-测试设施)\n",
    "- [7.4 文档测试](#7.4-文档测试)\n",
    "  - [7.4.1 文档测试用例](#7.4.1-文档测试用例)\n",
    "  - [7.4.2 运行文档测试](#7.4.2-运行文档测试)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "- 调试与测试的区别\n",
    "  - 调试\n",
    "    - 错误类型\n",
    "      - 语法错误\n",
    "      - 逻辑错误\n",
    "  - 测试\n",
    "    - 功能测试\n",
    "    - 性能测试\n",
    "    - ..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 7.1 调试方法\n",
    "\n",
    "### 7.1.1 利用 `print`调试程序\n",
    "\n",
    "- 当程序输出结果与预期不一致时，在可能出错的位置利用`print`语句输出关键的变量，查看其取值与预期是否一致\n",
    "\n",
    "- 例：可变默认参数陷阱"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def fun(values, lst=[]):\n",
    "    for v in values:\n",
    "        lst.append(v)\n",
    "    return lst"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "     两次调用的参数完全相同，但输出是不一致的"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[1, 2, 3]"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fun([1, 2, 3])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[1, 2, 3, 1, 2, 3]"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fun([1, 2, 3])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- 可以怀疑问题出在`lst`参数上"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def fun(values, lst=[]):\n",
    "    print('lst:', lst)    # <--- 输出lst的值\n",
    "    for v in values:\n",
    "        lst.append(v)\n",
    "    return lst"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "lst: []\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[1, 2]"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fun([1, 2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "lst: [1, 2]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[1, 2, 1, 2]"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fun([1, 2])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "- 原因：可变参数的默认值也是一个变量，而且多次调用函数使用的是同一个默认参数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 7.1.2 利用 `logging`调试程序\n",
    "\n",
    "- `print`的缺点\n",
    "  - 调试的`print`语句需删除或注释掉\n",
    "  - 与其他输出信息混杂在一块影响输出结果的判断\n",
    "- `logging`模块是Python内置的标准模块，主要用于输出运行日志，可以设置输出日志的等级、日志保存路径、日志文件回滚等\n",
    "  - 可以通过设置不同的日志等级来对输出信息进行控制；\n",
    "  - 可以定制输出信息的格式；\n",
    "  - 可以方便地将不同类型的输出信息输出到不出的位置，例如文件"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- `Logger`对象\n",
    "  - `logging`模块的功能主要通过`Logger`对象实现\n",
    "  - `Logger`对象不用直接实例化，利用`getLogger`函数可以获取一个`Logger`对象\n",
    "  - `getLogger`函数有一个参数`name`，用于指定`Logger`对象的名称。相同的`name`会返回同一个`Logger`对象\n",
    "- `Logger`对象的主要方法\n",
    "  - `fatal`\n",
    "  - `critical`\n",
    "  - `error`\n",
    "  - `warn`\n",
    "  - `info`\n",
    "  - `debug`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- `basicConfig`用于配置`Loger`对象，主要参数包括\n",
    "  - `level`：日志信息的等级\n",
    "  - `format`：日志信息输出格式\n",
    "- 日志信息的等级\n",
    "  - `FATAL`：致命错误；\n",
    "  - `CRITICAL`：特别严重的错误，如内存耗尽、磁盘空间为空等，一般很少使用；\n",
    "  - `ERROR`：一般的错误，如输入/输出操作失败；\n",
    "  - `WARNING`：发生很重要的事件，但是并不是错误时，如用户登录密码错误\n",
    "  - `INFO`：处理请求或者状态变化等日常事务\n",
    "  - `DEBUG`：调试过程中使用DEBUG等级"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- 格式化字符串\n",
    "\n",
    "| 属性名称    | 格式          | 说明                     |\n",
    "| :----------- | :------------- | :------------------------ |\n",
    "| `name`      | %(name)       | `Logger`对象名           |\n",
    "| `asctime`   | %(asctime)s   | 精确到毫秒的日志事件时间 |\n",
    "| `filename`  | %(filename)s  | 日志文件名               |\n",
    "| `pathname`  | %(pathname)s  | 日志文件的全路径名称     |\n",
    "| `funcName`  | %(funcName)s  | 日志输出所在的函数       |\n",
    "| `levelname` | %(levelname)s | 日志的等级               |\n",
    "| `levelno`   | %(levelno)s   | 日志等级信息             |\n",
    "| `lineno`    | %(lineno)d    | 日志输出在代码中的行号   |\n",
    "| `module`    | %(module)s    | 日志输出所在的模块名     |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2021-08-15 11:24:42,088 - INFO - [1, 2] - line: 12\n",
      "2021-08-15 11:24:42,090 - INFO - [1, 2, 1, 2] - line: 13\n"
     ]
    }
   ],
   "source": [
    "import logging\n",
    "logging.basicConfig(level=logging.INFO, # 日志级别为DEBUG\n",
    "                    format='%(asctime)s - %(levelname)s - %(message)s - line: %(lineno)d')\n",
    "logger = logging.getLogger()\n",
    "\n",
    "def fun(values, lst=[]):\n",
    "    logger.debug(lst)\n",
    "    for v in values:\n",
    "        lst.append(v)\n",
    "    return lst\n",
    "\n",
    "logger.info(fun([1, 2]))\n",
    "logger.info(fun([1, 2]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2021-08-15 11:24:42,868 - INFO - [1, 2] - line: 12\n",
      "2021-08-15 11:24:42,869 - INFO - [1, 2, 1, 2] - line: 13\n"
     ]
    }
   ],
   "source": [
    "import logging\n",
    "logging.basicConfig(level=logging.DEBUG, # 日志级别为INFO\n",
    "                    format='%(asctime)s - %(levelname)s - %(message)s - line: %(lineno)d')\n",
    "logger = logging.getLogger()\n",
    "\n",
    "def fun(values, lst=[]):\n",
    "    logger.debug(lst)\n",
    "    for v in values:\n",
    "        lst.append(v)\n",
    "    return lst\n",
    "\n",
    "logger.info(fun([1, 2]))\n",
    "logger.info(fun([1, 2]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 7.1.3 `pdb`调试器\n",
    "\n",
    "- `pdb`是Python内置的交互式调试器，它支持在源代码行级别设置断点、单步执行、列出源代码、查看变量值等功能\n",
    "- 运行`pdb`调试器的方法\n",
    "  - 命令方式：在命令行中执行`python -m pdb source_code.py`\n",
    "  - `pdb.set_trace()`：在源代码中导入`pdb`模块，然后在代码中希望设置断点的位置放置`pdb.set_trace()`\n",
    "  - `breakpoint()`：  使用方法与`pdb.set_trace`相似。但是，`breakpoint`是一个内置函数，不必导入`pdb`模块"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- `pdb`常用命令\n",
    "\n",
    "\n",
    "| 命令        | 命令简写 | 功能                               |\n",
    "| ----------- | -------- | ---------------------------------- |\n",
    "| `list`      | `l`      | 列出代码                           |\n",
    "| `next`      | `n`      | 单步执行，不进入函数内部           |\n",
    "| `step`      | `s`      | 单步执行，进入函数内部             |\n",
    "| `where`     | `w`      | 查看所在的位置                     |\n",
    "| `print`     | `p`      | 输出变量值                         |\n",
    "| `args`      | `a`      | 打印当前函数的参数                 |\n",
    "| `continue`  | `c`      | 继续运行，直到遇到断点或者程序结束 |\n",
    "| `return`    | `r`      | 一直运行到函数返回                 |\n",
    "| `jump`      | `j`      | 跳转到指定行数运行                 |\n",
    "| `break`     | `b`      | 添加断点/列出断点                  |\n",
    "| `clearl`    | `cl`     | 清除断点                           |\n",
    "| `disable`   | `d`      | 禁用断点                           |\n",
    "| `enable`    | --       | 启用断点                           |\n",
    "| `tbreak`    | --       | 设置临时断点（断点处只中断一次）   |\n",
    "| `condition` | --       | 条件断点                           |\n",
    "| `help`      | `h`      | 帮助                               |\n",
    "| `quit`      | `q`      | 退出pdb                            |\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### 利用IDE调试\n",
    "  - 常用的Python开发工具或IDE环境如PyCharm、Spyder、PyDev、VSCode等都有强大的调试功能，便用更加简单便捷"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 7.2 异常处理\n",
    "\n",
    "### 7.2.1 异常的原因\n",
    "- 程序运行过程中由于没有考虑到的意外情况而引发的错误\n",
    "- 常见导致异常的原因\n",
    "  - 数据类型不匹配、文件不存在、网络连接错误、除运算中分母为零、下标越界等\n",
    "- Python解释器在检测到异常之后，会根据异常的类型及异常信息，将其包装成一个异常对象"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "ename": "ZeroDivisionError",
     "evalue": "division by zero",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mZeroDivisionError\u001b[0m                         Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-13-9e1622b385b6>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;36m1\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mZeroDivisionError\u001b[0m: division by zero"
     ]
    }
   ],
   "source": [
    "1/0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- 主动抛出异常\n",
    "  - `raise`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "ename": "Exception",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mException\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-14-406393329809>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mException\u001b[0m  \u001b[0;31m# 异常类\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mException\u001b[0m: "
     ]
    }
   ],
   "source": [
    "raise Exception  # 异常类"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "ename": "Exception",
     "evalue": "程序出现错误！",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mException\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-15-969c2ee3e5d6>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'程序出现错误！'\u001b[0m\u001b[0;34m)\u001b[0m  \u001b[0;31m# 异常对象\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mException\u001b[0m: 程序出现错误！"
     ]
    }
   ],
   "source": [
    "raise Exception('程序出现错误！')  # 异常对象"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 7.2.2 断言\n",
    "\n",
    "- `assert`语句\n",
    "  - 用于判断一个表达式是否为真\n",
    "  - 当表达式为`True`对程序的执行没有影响，当表达式`False`时触发`AssertionError`异常\n",
    "- 语法形式\n",
    "  ```python\n",
    "  assert 表达式 [, 异常信息]\n",
    "  ```\n",
    "  相当于\n",
    "  ```python\n",
    "  if not expression:\n",
    "      raise AssertionError\n",
    "  ```\n",
    "  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "outputs": [],
   "source": [
    "def fun(param):\n",
    "    assert isinstance(param, str), '参数必须为字符串'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "fun('abc')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "ename": "AssertionError",
     "evalue": "参数必须为字符串",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mAssertionError\u001b[0m                            Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-18-a0060b8a4c19>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mfun\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m<ipython-input-16-c416ba3f78d7>\u001b[0m in \u001b[0;36mfun\u001b[0;34m(param)\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfun\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mparam\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m     \u001b[0;32massert\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mparam\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'参数必须为字符串'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mAssertionError\u001b[0m: 参数必须为字符串"
     ]
    }
   ],
   "source": [
    "fun(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 7.2.3 异常处理\n",
    "\n",
    "#### 异常处理的过程\n",
    "- 异常处理基本方式\n",
    "  ```python\n",
    "  try:\n",
    "      语句块1\n",
    "  except 异常类型:\n",
    "      语句块2\n",
    "  ```\n",
    "- 异常处理的过程\n",
    "    - 首先，执行“语句块1”中的语句。\n",
    "      - 若没有发生异常则跳过`except`子句，执行后续代码；\n",
    "      - 若发生异常，则跳过“语句块1”的其余代码，执行`except`子句；\n",
    "    - `except`子句判断“语句块1”中发生的异常的类型与“异常类型”是否一致\n",
    "      - 若不一致则继续抛出异常，程序崩溃退出；\n",
    "      - 若一致，则执行“语句块2”。“语句块2”中往往会输出错语信息，若有必要会中断程序运行，或者转而调用其他函数；\n",
    "    - 当`except`子句执行完之后，继续执行后续代码。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "outputs": [],
   "source": [
    "def division(x, y):\n",
    "    try:\n",
    "        return x/y\n",
    "    except ZeroDivisionError:\n",
    "        print(\"除数为0\")\n",
    "    return 0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.5"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "division(1, 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "除数为0\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "division(1, 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### 获取异常实例\n",
    "- `except`子句中可使用`as`来获取异常类的实例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "def division(x, y):\n",
    "    try:\n",
    "        return x/y\n",
    "    except ZeroDivisionError as e:\n",
    "        print(e,'\\n',\"除数为0\")\n",
    "    return 0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "division by zero \n",
      " 除数为0\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "division(1, 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### 捕获多种异常\n",
    "- 下面的代码，可能触发的异常有`ZeroDivisionError`和`AssertionError`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "def division(x, y):\n",
    "    assert y != 1\n",
    "    return x/y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- 使用更高级别的异常类\n",
    "  - `ZeroDivisionError`是`ArithmeticError`的子类，而`ArithmeticError`是`Exception`的子类；`AssertionError`也是`Exception`的子类\n",
    "  - 这种方法往往也会捕获到预期之外的异常，从而使得程序中的相关错误无法被发现"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "def division(x, y):\n",
    "    try:\n",
    "        assert y != 1, '分母为1'\n",
    "        return x/y\n",
    "    except Exception as e:\n",
    "        print(e)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "分母为1\n"
     ]
    }
   ],
   "source": [
    "division(1, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "division by zero\n"
     ]
    }
   ],
   "source": [
    "division(1, 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- 异常元组"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "def division(x, y):\n",
    "    try:\n",
    "        assert y != 1, '分母为1'\n",
    "        return x/y\n",
    "    except (AssertionError, Exception) as e:\n",
    "        print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- 使用多个`except`子句"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "def division(x, y):\n",
    "    try:\n",
    "        assert y != 1, '分母为1'\n",
    "        return x/y\n",
    "    except AssertionError as e:\n",
    "        print(e)\n",
    "        return x\n",
    "    except Exception as e:\n",
    "        print(e)\n",
    "        return 0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### `else`子句\n",
    "\n",
    "```\n",
    " try:\n",
    "      语句块1\n",
    " except 异常类型:\n",
    "      语句块2\n",
    " else:\n",
    "      语句块3\n",
    "```\n",
    "- 当“语句块1”中没有发生异常时，会执行`else`子句中的“语句块3”"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "请输入一个数字：\n",
      "输入错语，请再次尝试！\n",
      "请输入一个数字：\n",
      "输入错语，请再次尝试！\n",
      "请输入一个数字：\n",
      "输入错语，请再次尝试！\n",
      "请输入一个数字：1\n",
      "输入正确！\n"
     ]
    }
   ],
   "source": [
    "while True:\n",
    "    try:\n",
    "        value = input(\"请输入一个数字：\")\n",
    "        value = float(value)\n",
    "    except Exception as e:\n",
    "        print(\"输入错语，请再次尝试！\")\n",
    "    else:\n",
    "        print(\"输入正确！\")\n",
    "        break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### `finally`子句\n",
    "\n",
    "```\n",
    "try:\n",
    "      语句块1\n",
    " except 异常类型:\n",
    "      语句块2\n",
    " finally:\n",
    "      语句块4\n",
    "```\n",
    "- `finally`子句中的“代码块4”无论“语句块1”是否出现异常都会被执行"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 7.2.4 异常的类型\n",
    "\n",
    "#### 内置异常类型\n",
    "\n",
    "```\n",
    "BaseException                              # 所有异常类的基类\n",
    " +-- SystemExit\n",
    " +-- KeyboardInterrupt\n",
    " +-- GeneratorExit\n",
    " +-- Exception                             # 常规异常的基类\n",
    "      +-- StopIteration                    # 可迭代对象终止\n",
    "      +-- StopAsyncIteration\n",
    "      +-- ArithmeticError\n",
    "      |    +-- FloatingPointError          # 浮点运算异常\n",
    "      |    +-- OverflowError\n",
    "      |    +-- ZeroDivisionError           # 除数为0异常\n",
    "      +-- AssertionError\n",
    "      +-- AttributeError                   # 对象属性异常\n",
    "      +-- BufferError\n",
    "      +-- EOFError                         # 文件访问终止\n",
    "      +-- ImportError                      # 模块导入异常\n",
    "      |    +-- ModuleNotFoundError\n",
    "      +-- LookupError\n",
    "      |    +-- IndexError\n",
    "      |    +-- KeyError\n",
    "      +-- MemoryError\n",
    "      +-- NameError                        # 对象未声明/初始化\n",
    "      |    +-- UnboundLocalError\n",
    "      +-- OSError\n",
    "      |    +-- BlockingIOError\n",
    "      |    +-- ChildProcessError\n",
    "      |    +-- ConnectionError\n",
    "      |    |    +-- BrokenPipeError\n",
    "      |    |    +-- ConnectionAbortedError\n",
    "      |    |    +-- ConnectionRefusedError\n",
    "      |    |    +-- ConnectionResetError\n",
    "      |    +-- FileExistsError\n",
    "      |    +-- FileNotFoundError           # 文件不存在\n",
    "      |    +-- InterruptedError\n",
    "      |    +-- IsADirectoryError\n",
    "      |    +-- NotADirectoryError\n",
    "      |    +-- PermissionError             \n",
    "      |    +-- ProcessLookupError\n",
    "      |    +-- TimeoutError\n",
    "      +-- ReferenceError\n",
    "      +-- RuntimeError\n",
    "      |    +-- NotImplementedError\n",
    "      |    +-- RecursionError\n",
    "      +-- SyntaxError\n",
    "      |    +-- IndentationError\n",
    "      |         +-- TabError\n",
    "      +-- SystemError\n",
    "      +-- TypeError\n",
    "      +-- ValueError\n",
    "      |    +-- UnicodeError\n",
    "      |         +-- UnicodeDecodeError\n",
    "      |         +-- UnicodeEncodeError\n",
    "      |         +-- UnicodeTranslateError\n",
    "      +-- Warning\n",
    "           +-- DeprecationWarning\n",
    "           +-- PendingDeprecationWarning\n",
    "           +-- RuntimeWarning\n",
    "           +-- SyntaxWarning\n",
    "           +-- UserWarning\n",
    "           +-- FutureWarning\n",
    "           +-- ImportWarning\n",
    "           +-- UnicodeWarning\n",
    "           +-- BytesWarning\n",
    "           +-- ResourceWarning\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### 自定义异常\n",
    "\n",
    "- 自定义异常类通常派生自`Exception`类，并利用`raise`语句在满足一定条件的情况下主动触发\n",
    "- 大多数自定义异常类都仅用于确定程序错误的原因并显示异常信息，往往不需要定义复杂的功能"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "class ParameterException(Exception): \n",
    "    pass\n",
    "def greeting(info):\n",
    "    if not isinstance(info, str):\n",
    "        raise ParameterException(\"参 数 必 须 为 字 符 串!\") \n",
    "    print(info)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hello Python\n"
     ]
    }
   ],
   "source": [
    "greeting(\"Hello Python\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "ename": "ParameterException",
     "evalue": "参 数 必 须 为 字 符 串!",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mParameterException\u001b[0m                        Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-33-70f534de3be9>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mgreeting\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m<ipython-input-31-cebce8c89763>\u001b[0m in \u001b[0;36mgreeting\u001b[0;34m(info)\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mgreeting\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minfo\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      4\u001b[0m     \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minfo\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 5\u001b[0;31m         \u001b[0;32mraise\u001b[0m \u001b[0mParameterException\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"参 数 必 须 为 字 符 串!\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      6\u001b[0m     \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minfo\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mParameterException\u001b[0m: 参 数 必 须 为 字 符 串!"
     ]
    }
   ],
   "source": [
    "greeting(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 7.3 单元测试*\n",
    "\n",
    "### 7.3.1 单元测试的概念及工具\n",
    "\n",
    "- 单元测试（Unit Testing）是软件开发中使用的一种重要的自动化测试方法，也是测试驱动开发的必要过程\n",
    "- 测试单元\n",
    "  - 在单元测试中，每个测试单元仅关注一个较小的、独立的功能\n",
    "  - 在面向过程编程中，测试单元可以是一个程序、函数或者过程\n",
    "  - 在面向对象编程中，常常以方法作为测试单元。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### 常用单元测试框架\n",
    "- `unittest`\n",
    "  - Python标准库中自带的单元测试框架\n",
    "\n",
    "```python\n",
    "import unittest\n",
    "\n",
    "class TestStringMethods(unittest.TestCase):\n",
    "    def test_upper(self):\n",
    "        self.assertEqual('abc'.upper(), 'ABC')\n",
    "\n",
    "    def test_loser(self):\n",
    "        self.assertEqual('ABC'.lower(), 'abc')\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    unittest.main()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- `nose`\n",
    "  - `nose`是Python的一种第三方测试框架\n",
    "  - `pip install nose`\n",
    "\n",
    "```python\n",
    "import nose\n",
    "\n",
    "def test_upper():\n",
    "    assert 'abc'.upper() == 'ABC'\n",
    "\n",
    "def test_loser():\n",
    "    assert 'ABC'.lower() == 'abc'\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    nose.runmodule()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- `py.test`\n",
    "  - `pip install pytest`\n",
    "  \n",
    "```python\n",
    "import pytest\n",
    "\n",
    "def test_upper():\n",
    "    assert 'abc'.upper() == 'ABC'\n",
    "\n",
    "def test_loser():\n",
    "  assert 'ABC'.lower() == 'abc'\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    pytest.main([\"-s\",\"test_file_name.py\"])\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### 7.3.2  `unittest`基础\n",
    "\n",
    "- 测试用例（Test Case）\n",
    "  - 测试用例是独立的测试流程。在测试用例中，向测试目标输入特定的数据，检查返回结果与预期是否一致来验证程序的正确性\n",
    "- 测试设施（Test Fixture）\n",
    "  - 测试设施用于搭建和清理测试环境。测试用例中不同的测试方法可能会需要一些共用的资源，例如文件、数据库连接、输入数据等。测试设施在测试方法执行之前准备这些资源，并在测试方法运行结束后进行清理\n",
    "- 测试套件（Test Suite）\n",
    "  - 是一组测试用例或者其他测试套件的集合\n",
    "- 测试加载器（Test Loader）\n",
    "  - 用于从类和模型中创建测试套件\n",
    "- 测试运行器（Test Runner）\n",
    "  - 负责执行测试并控制测试结果输出"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- `unittest`模块中常用的类或函数包括：\n",
    "  - `unittest.TestCase`\n",
    "    - 所有测试用例类的基类；\n",
    "  - `unittest.main()`\n",
    "    - 该函数可以将一个单元测试模块变为可直接运行的测试脚本，它使用`TestLoader`类来搜索所有包含在该模块中命名以`test`开头的测试方法并自动执行他们。执行的默认顺序是根据方法名的ASCII码顺序；\n",
    "  - `unittest.TestSuite`\n",
    "    - 测试套件类；\n",
    "  - `unittest.TextTestRunner`\n",
    "    - 该类中的`run`方法运行测试套件中的测试用例；\n",
    "  - `unittest.defaultTestLoader`\n",
    "    - 该类中的`discover`方法根据匹配条件自动搜索测试目录中的测试用例文件，并将查找到的测试用例组装为测试套件；\n",
    "  - `unittest.skip`\n",
    "    - 装饰器，用于屏蔽测试用例中的暂时不需执行的测试方法。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### 7.3.3 创建测试用例\n",
    "\n",
    "- 通过继承自`unittest.TestCase`类可创建一个测试用例\n",
    "\n",
    "- 文件`math_method.py`\n",
    "\n",
    "```python\n",
    "class MathMethod():\n",
    "    def add(self,a,b):\n",
    "        return a+b\n",
    "    def sub(self,a,b):\n",
    "        return a-b\n",
    "```\n",
    "\n",
    "- 文件`test_case.py`\n",
    "\n",
    "```python\n",
    "import unittest\n",
    "from math_method import MathMethod\n",
    "class TestMathNMethod(unittest.TestCase):\n",
    "    def test_add_two_zero(self):\n",
    "        res=MathMethod().add(0,0)\n",
    "        print('两个0相加',res)\n",
    "        self.assertEqual(0,res)\n",
    "\n",
    "    def test_add_two_positive(self):\n",
    "        res=MathMethod().add(1,8)\n",
    "        print('两个正数相加',res)\n",
    "        self.assertEqual(9,res)\n",
    "    \n",
    "    def test_add_two_negative(self):\n",
    "        res=MathMethod().add(-1,-4)\n",
    "        print('两个负数相加',res)\n",
    "        self.assertEqual(-5,res)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- 常用断言方法\n",
    "\n",
    "| 断言方法                                | 功能                               |\n",
    "| :-------------------------------------- | :--------------------------------- |\n",
    "| `assertEqual(a, b)`                     | `a == b`                           |\n",
    "| `assertNotEqual(a, b)`                  | `a != b`                           |\n",
    "| `assertTrue(x)`                         | `bool(x) is True`                  |\n",
    "| `assertFalse(x)`                        | `bool(x) is False`                 |\n",
    "| `assertIs(a, b)`                        | `a is b`                           |\n",
    "| `assertIsNot(a, b)`                     | `a is not b`                       |\n",
    "| `assertIsNone(x)`                       | `x is None`                        |\n",
    "| `assertIsNotNone(x)`                    | `x is not None`                    |\n",
    "| `assertIn(a, b)`                        | `a in b`                           |\n",
    "| `assertNotIn(a, b)`                     | `a not in b`                       |\n",
    "| `assertIsInstance(a, b)`                | `isinstance(a, b)`                 |\n",
    "| `assertNotIsInstance(a, b)`             | `not isinstance(a, b)`             |\n",
    "| `assertRaises(exc, fun, *args, **kwds)` | `fun(*args, **kwds)`抛出异常 `exc` |\n",
    "| `assertGreater(a, b)`                   | `a > b`                            |\n",
    "| `assertGreaterEqual(a, b)`              | `a >= b`                           |\n",
    "| `assertLess(a, b)`                      | `a < b`                            |\n",
    "| `assertLessEqual(a, b)`                 | `a <= b`                           |\n",
    "| `assertListEqual(a, b)`                 | 列表`a`与`b`相等                   |\n",
    "| `assertTupleEqual(a, b)`                | 元组`a`与`b`相等                   |\n",
    "| `assertSetEqual(a, b)`                  | 集合`a`与`b`相等                   |\n",
    "| `assertDictEqual(a, b)`                 | 字典`a`与`b`相等                   |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 7.3.4 运行测试用例\n",
    "\n",
    "- 模块内执行\n",
    "  - 在测试用例所在的模块中添加如下代码，直接运行模块\n",
    "  ```python\n",
    "  if __name__ == '__main__':\n",
    "      unittest.main()\n",
    "  ```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 命令行运行\n",
    "  - `python -m unittest test_module`\n",
    "    - 扩展名`.py`可以省去\n",
    "  - `python -m unittest test_module.TestCaseClass`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 7.3.5 测试套件的创建与执行\n",
    "\n",
    "- 测试代码\n",
    "\n",
    "```python\n",
    "import unittest\n",
    "from unit_test import TestMathNMethod\n",
    "\n",
    "suite = unittest.TestSuite()     # 测试套件\n",
    "loader = unittest.TestLoader()   # 测试加载器\n",
    "suite.addTest(loader.loadTestsFromTestCase(TestMathNMethod))\n",
    "\n",
    "file = open('test_result.txt', 'w+')\n",
    "runner = unittest.TextTestRunner(stream=file, verbosity=2)  # verbosity用于控制测试报告的详细程度\n",
    "runner.run(suite)\n",
    "```\n",
    "\n",
    "- 运行结果\n",
    "\n",
    "```\n",
    "test_add_two_negative (unit_test.TestMathNMethod) ... ok\n",
    "test_add_two_positive (unit_test.TestMathNMethod) ... ok\n",
    "test_add_two_zero (unit_test.TestMathNMethod) ... ok\n",
    "\n",
    "----------------------------------------------------------------------\n",
    "Ran 3 tests in 0.002s\n",
    "\n",
    "OK\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- 利用测试套件控制测试方法的执行顺序\n",
    "\n",
    "```python\n",
    "import unittest\n",
    "from unit_test import TestMathNMethod\n",
    "\n",
    "suite = unittest.TestSuite()\n",
    "tests = [\n",
    "    TestMathNMethod('test_add_two_zero'),\n",
    "    TestMathNMethod('test_add_two_negative'),\n",
    "    TestMathNMethod('test_add_two_positive')\n",
    "]\n",
    "\n",
    "suite.addTests(tests)\n",
    "\n",
    "runner = unittest.TextTestRunner(verbosity=2)\n",
    "runner.run(suite)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### 7.3.6 测试设施\n",
    "\n",
    "- `unittest`中测试用例的测试设施通过方法`setUp`和`tearDown`实现\n",
    "  - `setUp`方法在所有测试方法执行之前运行，用于准备所有测试方法共同的资源\n",
    "  - `tearDown`方法在所有测试方法执行毕后执行，用于作一些清理工作\n",
    "\n",
    "```python\n",
    "import unittest\n",
    "from math_method import MathMethod\n",
    "\n",
    "class TestMathNMethod(unittest.TestCase):\n",
    "    def setUp(self):\n",
    "        self.unit = MathMethod()\n",
    "\n",
    "    def tearDown(self):\n",
    "        del self.unit\n",
    "\n",
    "    def test_add_two_zero(self):\n",
    "        res = self.unit.add(0, 0)\n",
    "        print('两个0相加', res)\n",
    "        self.assertEqual(0, res)\n",
    "\n",
    "    def test_add_two_positive(self):\n",
    "        res = self.unit.add(1, 8)\n",
    "        print('两个正数相加', res)\n",
    "        self.assertEqual(9, res)\n",
    "\n",
    "    def test_add_two_negative(self):\n",
    "        res = self.unit.add(-1, -4)\n",
    "        print('两个负数相加', res)\n",
    "        self.assertEqual(-5, res)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 7.4 文档测试\n",
    "\n",
    "- doctest\n",
    "  - 是Python标准库自带的一种测试工具，可以用于简单的单元测试\n",
    "  - 其工作原理是在函数或类的文档字符串中寻找测试用例并执行，比较输出结果与期望值是否相匹配\n",
    "\n",
    "### 7.4.1 文档测试用例\n",
    "\n",
    "- 文档字符串中“`>>>`”符号之 后为待执行的测试代码，紧接着为测试代码的预期输出\n",
    "\n",
    "```python\n",
    "def division(x, y):\n",
    "    '''\n",
    "    除法运算\n",
    "    Args:\n",
    "        x: 数值1\n",
    "        y: 数值2\n",
    "\n",
    "    Example:\n",
    "    >>> division(1, 1)\n",
    "    1.0\n",
    "    >>> division(1, 0)\n",
    "    除数为0\n",
    "    0\n",
    "    >>> division(2, 1)\n",
    "    2.0\n",
    "    '''\n",
    "    try:\n",
    "        return x/y\n",
    "    except ZeroDivisionError:\n",
    "        print(\"除数为0\")\n",
    "    return 0\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 7.4.2 运行文档测试\n",
    "- 模块内运行\n",
    "  - 在模块中添加如下代码，即可运行文档测试\n",
    "\n",
    "```python\n",
    "if __name__ == '__main__':\n",
    "    import doctest\n",
    "    doctest.testmod()\n",
    "```\n",
    "\n",
    "- 命令行运行\n",
    "  - `python -m unittest doc_test_module`\n",
    "    - 扩展名`.py`不是必须的"
   ]
  }
 ],
 "metadata": {
  "author": "liuchen",
  "celltoolbar": "幻灯片",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.1"
  },
  "rise": {
   "enable_chalkboard": true,
   "footer": "",
   "progress": true,
   "scroll": true,
   "slideNumber": true
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": false,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "url": "https://github.com/hitlic/python_book"
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
